package com.sz.biz.pay.account.service.impl;

import com.sz.biz.common.constants.ErrorCodes;
import com.sz.biz.common.constants.ShortName;
import com.sz.biz.pay.account.dto.PayAccountDto;
import com.sz.biz.pay.account.dto.PaySystemAccountDto;
import com.sz.biz.pay.account.entity.Account;
import com.sz.biz.pay.account.service.AccountService;
import com.sz.biz.pay.account.service.ThirdPayAccountService;
import com.sz.biz.pay.constants.PayConstant;
import com.sz.biz.pay.exception.LockPayAccountException;
import com.sz.biz.pay.exception.RetryPayPasswordException;
import com.sz.common.base.cache.redis.RedisPoolManager;
import com.sz.common.base.dao.ParamData;
import com.sz.common.base.dto.QResultDto;
import com.sz.common.base.dto.QueryParams;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.service.AbstractService;
import com.sz.common.base.utils.CheckDataUtils;
import com.sz.common.base.utils.CredentialUtils;
import com.sz.common.base.utils.StringUtils;
import com.sz.common.core.constants.SysErrorCodes;
import com.sz.common.core.service.PrincipalUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Function: Account Service Impl <br>
 * Author: siqishangshu <br>
 * Date: 2017-11-02 15:41:00
 */
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class AccountServiceImpl extends AbstractService implements AccountService {

    @Value("${sysPayPasswordRetryCache}")
    private String sysPayPasswordRetryKey;

    @Value("${sysPayLock}")
    private String sysPayLockKey;

    @Value("${sysPayLockTime}")
    private String sysPayLockTimeKey;

    @Autowired
    private ThirdPayAccountService thirdPayAccountService;

    @Override
    public int insert(Account entry) {
        return dao.save(getMapperNamespace() + ShortName.INSERT, entry);
    }

    @Override
    public int delete(String id) {
        return dao.delete(getMapperNamespace() + ShortName.DELETE_BY_ID, id);
    }

    @Override
    public int delete(List ids) {
        return dao.batchDelete(getMapperNamespace() + ShortName.DELETE_BY_IDS, ids);
    }

    @Override
    public int update(Account entry) {
        return dao.update(getSqlName("updateByPrimaryKeySelective"),entry);
    }

    @Override
    public Account select(String id) {
        return (Account) dao.findForObject(getMapperNamespace() + ShortName.SELECT_BY_ID, id);
    }

    @Override
    public List<Account> selectList(List ids) {
        return null;
    }

    @Override
    public List<Account> selectAll() {
        return null;
    }

    @Override
    public QResultDto selectList(QueryParams param) {
        return null;
    }

    @Override
    public int count(QueryParams param) {
        return 0;
    }

    @Override
    public int countAll() {
        return 0;
    }

    @Override
    public void save(Account payAccount) {
        if (payAccount != null) {
            valid(payAccount);
            payAccount.setCreateTime(dao.getDbDate());
            dao.save(getSqlName("insertSelective"), payAccount);
        }
    }

    private String encryptionProc(String pswd, String credentialSalt) {
        return new Md5Hash(pswd, credentialSalt).toString();
    }

    public void valid(Account payAccount) {
        if (payAccount != null) {
            if (payAccount.getCustomerId() == null || payAccount.getCustomerId() == 0) {
                throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_EMPTY, "customerId");
            }
            if (StringUtils.isEmpty(payAccount.getCustName())) {
                throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_EMPTY, "custName");
            }
            if (StringUtils.isEmpty(payAccount.getAccountType())) {
                throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_EMPTY, "accountType");
            }
        }
    }

    @Override
    public void resetPassword(String password, String repeatPassword, String id) {
        if (!password.equals(repeatPassword)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_PAY_PASSWORD_NOT_MATCH);
        }
        if (!CheckDataUtils.checkPassword(password)) {
            throw Exceptions.bizException(SysErrorCodes.ERROR_MODEL_USER_PASSWORD_FORMAT_ERROR);
        }
        Account payAccount = findById(id);
        if (payAccount == null) {
            throw Exceptions.bizException(ErrorCodes.ERROR_PAY_ACCOUNT_NOT_EXISTS);
        }
        if (StringUtils.isEmpty(payAccount.getSalt())) {
            String credentialSalt = CredentialUtils.generateSalt();
            payAccount.setSalt(credentialSalt);
        }
        Account saveData = new Account();
        saveData.setPassword(encryptionProc(password, payAccount.getSalt()));
        saveData.setId(id);
        saveData.setLastUpdateUserId(PrincipalUtils.getAccountId());
        saveData.setLastUpdateTime(dao.getDbDate());
        dao.update(getSqlName("updateByPrimaryKeySelective"), saveData);
    }

    @Override
    public Account findByCustomerId(int customerId, int accountType) {
        ParamData pd = new ParamData();
        pd.put("customerId", customerId);
        pd.put("accountType", accountType);
        return (Account) dao.findForObject(getSqlName("findByCustomerId"), pd);
    }

    @Override
    public Account findById(String accountId) {
        return (Account) dao.findForObject(getSqlName("selectByPrimaryKey"), accountId);
    }

    @Override
    public void setPassword(String password, String accountId) {
        ParamData pd = new ParamData();
        if (!CheckDataUtils.checkPassword(password)) {
            throw Exceptions.bizException(SysErrorCodes.ERROR_MODEL_USER_PASSWORD_FORMAT_ERROR);
        }
        String credentialSalt = CredentialUtils.generateSalt();
        password = encryptionProc(password, credentialSalt);
        pd.put("password", password);
        pd.put("salt", credentialSalt);
        pd.put("id", accountId);
        dao.update(getSqlName("setPassword"), pd);
    }


    @Override
    public boolean isNeedSetPassword(String accountId) {
        String password = (String) dao.findForObject(getSqlName("getPassword"), accountId);
        if (StringUtils.isEmpty(password)) {
            removeRetryPayPssword(accountId);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public List<Account> findByAccountType(int accountType) {
        return dao.findForList(getSqlName("findByAccountType"), accountType, Account.class);
    }

    @Override
    public boolean checkOldPassword(String oldPassword, String accountId) {
        Account account = findById(accountId);
        if (account == null) {
            throw Exceptions.bizException(ErrorCodes.ERROR_PAY_ACCOUNT_NOT_EXISTS);
        }
        if (isNeedSetPassword(accountId)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_PAY_INIT_PASSWORD);
        }
        if (!checkAccountIsLocked(accountId)) {
            String oldHash = new Md5Hash(oldPassword, account.getSalt()).toString();
            if (oldHash.equals(account.getPassword())) {
                removeRetryPayPssword(accountId);
                return true;
            }
        }
        processRetry(accountId);
        return false;
    }

    @Override
    public QResultDto find(String q, boolean isAsc, int pageIndex, int pageSize, Integer userType, String thirdType, int accountType) {
        List<Integer> userTypes = getUserTypes(userType);
        Map<String, Object> map = new HashMap<>();
        map.put("userTypes", userTypes);
        map.put("thirdType", thirdType);
        map.put("accountType", accountType);
        ParamData pd = this.convertQueryParams(new QueryParams(pageSize, pageIndex, q, "", isAsc, null).put(map));
        List<PayAccountDto> list = dao.findForList(getSqlName("findByParamListPage"), pd, PayAccountDto.class);
        QResultDto qResultDto = new QResultDto();
        qResultDto.setData(list);
        qResultDto.setPagination(pd.getPagination());
        return qResultDto;
    }

    @Override
    public List<PayAccountDto> findPayAccountDtoByIdAndThirdType(String accountId, String thirdType, int accountType) {
        if (StringUtils.isEmpty(accountId)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_EMPTY, "accountId");
        }
        if (StringUtils.isEmpty(thirdType)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_EMPTY, "thirdType");
        }
        ParamData pd = new ParamData();
        pd.put("id", accountId);
        pd.put("thirdType", thirdType);
        pd.put("accountType", accountType);
        List<PayAccountDto> payAccountDto = dao.findForList(getSqlName("findByIdAndThirdType"), pd, PayAccountDto.class);
        return payAccountDto;
    }

    private List<Integer> getUserTypes(Integer userType) {
        List<Integer> userTypes;
        if (userType == null) {
            userType = 1;
        }
        switch (userType) {
            case 1:
                userTypes = Arrays.asList(1, 3);
                break;
            case 2:
                userTypes = Arrays.asList(2, 3, 256);
                break;
            default:
                userTypes = Arrays.asList(1);
        }
        return userTypes;
    }


    void processRetry(String accountId) {
        Jedis jedis = RedisPoolManager.getJedis();
        if (checkAccountIsLocked(accountId)) {
            throw new LockPayAccountException();
        }
        int tryTimes = 0;
        if (jedis.exists(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId())) {
            String times = jedis.get(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId());
            try {
                tryTimes = Integer.parseInt(times);
            } catch (Exception e) {
                tryTimes = 0;
            }
        }
        tryTimes++;
        if (tryTimes >= PayConstant.PAYPWDRETRYTIMES) {
            jedis.close();
            lockAccount(accountId);
            throw new LockPayAccountException();
        } else {
            int leftTimes = PayConstant.PAYPWDRETRYTIMES - tryTimes;
            SecurityUtils.getSubject().getSession().setAttribute("PayleftInputCount", leftTimes);
            jedis.set(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId(), tryTimes + "");
            jedis.close();
            throw new RetryPayPasswordException();
        }
    }

    void removeRetryPayPssword(String accountId) {
        Jedis jedis = RedisPoolManager.getJedis();
        jedis.del(sysPayLockKey + accountId + PrincipalUtils.getAccountId());
        jedis.del(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId());
        jedis.del(sysPayLockTimeKey + accountId + PrincipalUtils.getAccountId());
        jedis.close();
    }

    @Override
    public boolean checkAccountIsLocked(String accountId) {
        Jedis jedis = RedisPoolManager.getJedis();
        if (jedis.exists(sysPayLockKey + accountId + PrincipalUtils.getAccountId())) {
            jedis.close();
            return true;
        } else {
            jedis.del(sysPayLockTimeKey + accountId + PrincipalUtils.getAccountId());
            jedis.close();
            return false;
        }
    }

    @Override
    public void sysRecharge(PaySystemAccountDto paySystemAccountDto, String currentAccountId) {
        boolean checkResult = checkOldPassword(paySystemAccountDto.getPassword(), currentAccountId);
        if (checkResult) {
            thirdPayAccountService.recharge(paySystemAccountDto.getThirdId(), paySystemAccountDto.getTotalBalance(), paySystemAccountDto.getTransactionType(), paySystemAccountDto.getTransactionBalance(), PayConstant.AmountType.FREEZEBALANCE);
        }
    }

    /**
     * 根据id查询账户
     *
     * @param id 账户id
     * @return
     */
    @Override
    public Account findAccountById(String id) {
        return (Account) dao.findForObject(getSqlName("findAccountById"), id);
    }

    /**
     * 根据客户id查询账户
     * @param customerId
     * @return
     */
    @Override
    public Account findAccountByCustomerId(int customerId) {
        return (Account) dao.findForObject(getSqlName("findAccountByCustomerId"),customerId);
    }

    void lockAccount(String accountId) {
        Jedis jedis = RedisPoolManager.getJedis();
        jedis.setnx(sysPayLockKey + accountId + PrincipalUtils.getAccountId(), accountId);
        jedis.expire(sysPayLockKey + accountId + PrincipalUtils.getAccountId(), 60 * 60 * 24);
        jedis.set(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId(), PayConstant.PAYPWDRETRYTIMES + "");
        jedis.expire(sysPayPasswordRetryKey + accountId + PrincipalUtils.getAccountId(), 60 * 60 * 24);
        Calendar now = Calendar.getInstance();
        now.add(Calendar.HOUR, PayConstant.PAYACCOUNTLOCKTIMES);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = sdf.format(now.getTimeInMillis());
        jedis.setnx(sysPayLockTimeKey + accountId + PrincipalUtils.getAccountId(), dateStr);
        jedis.expire(sysPayLockTimeKey + accountId + PrincipalUtils.getAccountId(), 60 * 60 * 24);
        jedis.close();
    }


    @Override
    protected String getMapperNamespace() {
        return "com.sz.biz.pay.account.AccountMapper";
    }

}
